package api;

import org.basex.core.BaseXException;

/**
 * This class handles the <b>cache</b> collection
 * 
 * @author Football-Statistics Team
 */
public class Cache {
    private DatabaseManager dm;

    /**
    * Constructor
    * @param dm DatabaseManager
    */
    public Cache(DatabaseManager dm) {
        this.dm = dm;
    }
    
    /**
    * DatabaseManager setter
    * @param dm DatabaseManager
    */    
    public void setDatabaseManager(DatabaseManager dm) {
        this.dm = dm;
    }
    
    /**
    * Reads cached file
    * @param id ID of cached file
    * @return well-formed xml-string
    * @throws BaseXException on database error
    */    
    public String read(String id) throws BaseXException {
        return this.dm.queryCollection("collection('cache')/cache[@id='"+id+"']/*");
    }

    /**
    * Writes file to cache
    * @param id file ID
    * @param xml well-formed xml-string
    * @throws BaseXException on database error
    */     
    public void write(String id, String xml) throws BaseXException {
        xml = "<cache id='"+id+"'>" + xml + "</cache>";
        this.dm.addXML("cache", id, xml);
    }
    
    /**
    * Creates a new <b>cache</b> collection
    * @throws BaseXException on database error
    */ 
    public void createCache() throws BaseXException {
        this.dm.createCollection("cache");
    }
    
    /**
    * Clears the whole <b>cache</b> collection
    * @throws BaseXException on database error
    */ 
    public void clearCache() throws BaseXException {
        this.dm.removeCollection("cache");
        this.dm.createCollection("cache");
    }

    /**
    * Clears the cache of a given league
    * @param lid league ID
    * @throws BaseXException on database error
    */     
    public void clearCacheByLID(String lid) throws BaseXException {
        this.dm.removeXML("cache", "matches-" + lid);
        this.dm.removeXML("cache", "standings-both-full-" + lid);
        this.dm.removeXML("cache", "standings-both-half-" + lid);
        this.dm.removeXML("cache", "standings-home-full-" + lid);
        this.dm.removeXML("cache", "standings-home-half-" + lid);
        this.dm.removeXML("cache", "standings-guests-full-" + lid);
        this.dm.removeXML("cache", "standings-both-half-" + lid);
        this.dm.removeXML("cache", "teams-progress-" + lid);
        this.dm.removeXML("cache", "players-statistics-" + lid);        
    }
    
    /**
    * Updates the cache of a given league
    * this operation is time-consuming
    * @param lid league ID
    * @throws BaseXException on database error
    */     
    public void updateCacheByLID(String lid) throws BaseXException {
        this.clearCacheByLID(lid);
        this.cacheMatches(lid);
        this.cacheStandings(lid, "both", "full");
        this.cacheStandings(lid, "both", "half");
        this.cacheStandings(lid, "home", "full");
        this.cacheStandings(lid, "home", "half");
        this.cacheStandings(lid, "guests", "full");
        this.cacheStandings(lid, "guests", "half");
        this.cacheTeamsProgress(lid);
        this.cachePlayersStatistics(lid);                
    }

    /**
    * Caches matches of given league
    * @param lid league ID
    * @throws BaseXException on database error
    */     
    private void cacheMatches(String lid) throws BaseXException {
        String xml = "";
        String cacheID = "matches-" + lid;
        String query = "for $doc in collection('"+lid+"') return <match id=\"{data($doc//match/@id)}\">{$doc//date}{$doc//time}{$doc//home}{$doc//guests}<score>{data($doc//full/@home)}:{data($doc//full/@guests)}</score></match>";
        xml = this.dm.queryCollection(query);
        xml = "<matches><lid>"+lid+"</lid>"+xml+"</matches>";
        this.write(cacheID, xml);
    }    

    /**
    * Caches stadings tables of given league
    * @param lid league ID
    * @param hg home/guests/both
    * @param fh full/half
    * @throws BaseXException on database error
    */     
    private void cacheStandings(String lid, String hg, String fh) throws BaseXException {
        String xml = "";
        String cacheID = "standings-" + hg + "-" + fh + "-" + lid;    
        String query = "for $team in distinct-values(collection('"+lid+"')//home) "
                + (!hg.equals("guests") ? "let $games:=count(collection('"+lid+"')//match[data(home)=$team]) + " : "let $games:=") + (!hg.equals("home") ? "count(collection('"+lid+"')//match[data(guests)=$team]) ": "0 ")
                + (!hg.equals("guests") ? "let $wins:=count(collection('"+lid+"')//match[data(home)=$team and score/"+fh+"/@home>score/"+fh+"/@guests]) + " : "let $wins:=") + (!hg.equals("home") ? "count(collection('"+lid+"')//match[data(guests)=$team and score/"+fh+"/@guests>score/"+fh+"/@home]) ": "0 ")
                + (!hg.equals("guests") ? "let $losses:=count(collection('"+lid+"')//match[data(home)=$team and score/"+fh+"/@home<score/"+fh+"/@guests]) + " : "let $losses:=") + (!hg.equals("home") ? "count(collection('"+lid+"')//match[data(guests)=$team and score/"+fh+"/@guests<score/"+fh+"/@home]) ": "0 ")
                + (!hg.equals("guests") ? "let $draws:=count(collection('"+lid+"')//match[data(home)=$team and score/"+fh+"/@home=score/"+fh+"/@guests]) + " : "let $draws:=") + (!hg.equals("home") ? "count(collection('"+lid+"')//match[data(guests)=$team and score/"+fh+"/@guests=score/"+fh+"/@home]) ": "0 ")
                + (!hg.equals("guests") ? "let $shot:=sum(collection('"+lid+"')//match[data(home)=$team]/score/"+fh+"/@home) + " : "let $shot:=") + (!hg.equals("home") ? "sum(collection('"+lid+"')//match[data(guests)=$team]/score/"+fh+"/@guests) ": "0 ")
                + (!hg.equals("guests") ? "let $got:=sum(collection('"+lid+"')//match[data(home)=$team]/score/"+fh+"/@guests) + " : "let $got:=") + (!hg.equals("home") ? "sum(collection('"+lid+"')//match[data(guests)=$team]/score/"+fh+"/@home) ": "0 ")
                + "let $points:=$wins*3+$draws "
                + "let $diff:=($shot - $got) "
                + "order by $points descending, $diff descending, $shot descending "
                + "return <team><name>{$team}</name><games>{$games}</games><wins>{$wins}</wins><draws>{$draws}</draws><losses>{$losses}</losses><score>{$shot}:{$got}</score><points>{$points}</points></team>";
        xml = this.dm.queryCollection(query);
        xml = "<standings>"+xml+"</standings>";
        this.write(cacheID, xml);
    }
    
    /**
    * Caches teams progress for given league
    * @param lid league ID
    * @throws BaseXException on database error
    */     
    private void cacheTeamsProgress(String lid) throws BaseXException {
        String xml = "";
        String cacheID = "teams-progress-" + lid;        
        String query = "for $round in distinct-values(collection('"+lid+"')//round) "
                + "let $teams_points := "
                    + "for $team in distinct-values(collection('"+lid+"')//home) "
                    + "let $wins:=count(collection('"+lid+"')//match[data(home)=$team and score/full/@home>score/full/@guests and number(round)<=$round]) + count(collection('"+lid+"')//match[data(guests)=$team and score/full/@guests>score/full/@home and number(round)<=$round]) "
                    + "let $draws:=count(collection('"+lid+"')//match[data(home)=$team and score/full/@home=score/full/@guests and number(round)<=$round]) + count(collection('"+lid+"')//match[data(guests)=$team and score/full/@guests=score/full/@home and number(round)<=$round]) "
                    + "let $shot:=sum(collection('"+lid+"')//match[data(home)=$team]/score/full/@home) + sum(collection('"+lid+"')//match[data(guests)=$team]/score/full/@guests) "
                    + "let $got:=sum(collection('"+lid+"')//match[data(home)=$team]/score/full/@guests) + sum(collection('"+lid+"')//match[data(guests)=$team]/score/full/@home) "
                    + "let $diff:=($shot - $got) "
                    + "let $points:=$wins*3+$draws "
                    + "order by $points descending, $diff descending, $shot descending "
                    + "return <team name=\"{$team}\" points=\"{$points}\" /> "
                + "return <round number=\"{$round}\">{$teams_points}</round>";
        xml = this.dm.queryCollection(query);
        xml = "<teams-progress>"+xml+"</teams-progress>";
        this.write(cacheID, xml);  
    }
    
    /**
    * Caches player statistics
    * @param lid league ID
    * @throws BaseXException on database error
    */     
    private void cachePlayersStatistics(String lid) throws BaseXException{
        String xml = "";
        String cacheID = "players-statistics-" + lid;
        String query = "for $player in distinct-values(collection('"+lid+"')//player/@name) for $team in "
                + "(let $home := distinct-values(collection('"+lid+"')//player[@name = $player]/..[name() = \"home-team\"]/../home/text()) "
                + "let $guests := distinct-values(collection('"+lid+"')//player[@name = $player]/..[name() = \"guest-team\"]/../guests/text()) "
                + "return  distinct-values(($home, $guests))) "
                + "let $goals := count(collection('"+lid+"')//goal[@player = $player and ((@team = \"1\" and ../../home/text()=$team) or (@team = \"2\" and ../../guests/text()=$team))]) "
                + "let $redCards := count(collection('"+lid+"')//card[@type=\"red\" and @player = $player and ((@team = \"1\" and ../../home/text()=$team) or (@team = \"2\" and ../../guests/text()=$team))]) "
                + "let $yellowCards := count(collection('"+lid+"')//card[@type=\"yellow\" and @player = $player and ((@team = \"1\" and ../../home/text()=$team) or (@team = \"2\" and ../../guests/text()=$team))]) "
                + "let $gamesPlayed := count(collection('"+lid+"')//player[@name = $player and ((../name() =\"home-team\" and ../../home/text()=$team) or (../name() =\"guest-team\" and ../../guests/text()=$team))]) "
                + "let $minsPlayed := $gamesPlayed*90 -( sum( for $min in (collection('"+lid+"')//change[@in=$player and ((@team = \"1\" and ../../home/text()=$team) or (@team = \"2\" and ../../guests/text()=$team) )]/@minute) return number(normalize-space(substring(($min),1,2)))) ) +  "
                + "sum( for $min in (collection('"+lid+"')//changes/change[@out=$player and ((@team = \"1\" and ../../home/text()=$team) or (@team = \"2\" and ../../guests/text()=$team) )]/@minute) return 90-number(normalize-space(substring(($min),1,2)) ) ) "
                + "order by ($player) ascending return <player name=\"{$player}\"><team>{$team}</team><goals>{$goals}</goals><games>{$gamesPlayed}</games>"
                + "<minutes>{$minsPlayed}</minutes><cards><yellow>{$yellowCards}</yellow><red>{$redCards}</red></cards></player>";
        xml = dm.queryCollection(query);
        xml = "<playerStatistics>\n" + xml + "\n</playerStatistics>";
        this.write(cacheID, xml);
    }
}
